#pragma once

#include <iostream>
#include <string>
#include <mutex>
#include <jsoncpp/json/json.h>
#include <fstream>
#include <algorithm>
#include "oj_model.hpp"
#include "oj_view.hpp"
#include "../comm/httplib.h"

namespace ns_control
{
    using namespace ns_model;
    using namespace ns_view;
    using namespace httplib;

    enum status
    {
        ONLINE,
        OFFLINE
    };

    class Machine
    {
    public:
        // 增加主机负载
        void IncLoad()
        {
            if (mtx)
                mtx->lock();
            ++load;
            if (mtx)
                mtx->unlock();
        }
        // 减少主机负载
        void DecLoad()
        {
            if (mtx)
                mtx->lock();
            --load;
            if (mtx)
                mtx->unlock();
        }
        void ResetLoad()
        {
            if (mtx)
                mtx->lock();
            load = 0;
            if (mtx)
                mtx->unlock();
        }
        // 获取主机负载
        uint64_t GetLoad()
        {
            uint64_t curload;
            if (mtx)
                mtx->lock();
            curload = load;
            if (mtx)
                mtx->unlock();
            return curload;
        }
        ~Machine()
        {
            // ...
        }

    public:
        int id; // 主机id
        std::string ip;
        int port;
        enum status status = OFFLINE;
        uint64_t load = 0; // 负载
        std::mutex *mtx = nullptr;
    };

    const std::string service = "./conf/service_machines.conf";

    // 负载均衡模块
    class LoadBlance
    {
    public:
        LoadBlance()
        {
            assert(LoadConf()); // 加载配置文件
        }
        bool LoadConf()
        {
            std::ifstream in(service, std::ifstream::in);
            if (!in.is_open())
            {
                LOG(FATAL, "加载主机配置文件失败");
                return false;
            }
            std::string line;
            while (getline(in, line))
            {
                std::vector<std::string> tokens;
                StringUtil::Spilit(line, tokens, ":");
                if (tokens.size() != 2)
                {
                    LOG(WARNING, "切分" + line + "失败");
                    return false;
                }
                Machine m;
                m.ip = tokens[0];
                m.port = std::stoi(tokens[1]);
                m.status = OFFLINE;     // 默认都是下线
                m.mtx = new std::mutex; // 记得释放
                m.id = _machines.size();
                _machines.push_back(std::move(m));
            }
            in.close();
            LOG(INFO, "加载主机配置文件成功");
            return true;
        }
        int AutoChoose(Machine *&machine)
        {
            _mtx.lock();
            if (_count == 0)
            {
                LOG(FATAL, "所有主机全部下线，请及时核查原因");
                _mtx.unlock();
                return -1;
            }
            int id = 0;
            int min_load = INT_MAX;
            for (int i = 0; i < _machines.size(); ++i)
            {
                // 当前主机下线就选择另一台主机
                if (_machines[i].status == OFFLINE)
                    continue;
                int load = _machines[i].GetLoad();
                if (load < min_load)
                {
                    min_load = load;
                    id = i;
                }
            }
            _mtx.unlock();
            machine = &_machines[id];
            return id;
        }
        void Online(int id)
        {
            _mtx.lock();
            _machines[id].status = ONLINE;
            LOG(INFO, "主机" + std::to_string(id) + "已经上线 详情" + _machines[id].ip + ":" + std::to_string(_machines[id].port));
            _count++;
            _mtx.unlock();
        }
        void Offline(int id)
        {
            _mtx.lock();
            _machines[id].status = OFFLINE;
            _count--;
            _machines[id].ResetLoad();
            _mtx.unlock();
        }
        void ShowMachines()
        {
            _mtx.lock();
            std::cout << "-------------------online-------------------" << std::endl;
            for (auto &m : _machines)
            {
                if (m.status == ONLINE)
                    std::cout << m.id << " ";
            }
            std::cout << std::endl;
            std::cout << "-------------------offline-------------------" << std::endl;
            for (auto &m : _machines)
            {
                if (m.status == OFFLINE)
                    std::cout << m.id << " ";
            }
            std::cout << std::endl;
            _mtx.unlock();
        }

    public:
        std::vector<Machine> _machines; // 可以提供服务的所有主机，下标充当主机id
        int _count = 0;                 // 在线主机数
        std::mutex _mtx;
    };

    class Control
    {
    public:
        bool GetAllQuestionsListHtml(std::string &html)
        {
            std::vector<Question> qs;
            if (_model.GetAllQuestions(qs))
            {
                // 对qs进行排序
                sort(qs.begin(), qs.end(), [](const Question &q1, const Question &q2) -> bool
                     { return stoi(q1.num) < stoi(q2.num); });
                // 使用view生成网页
                _view.ExpandAllQuestionsHtml(qs, html);
                return true;
            }
            else
            {
                return false;
            }
        }
        bool GetOneQuestionByNumberHtml(const std::string &number, std::string &html)
        {
            Question q;
            if (_model.GetOneQuestionByNumber(number, q))
            {
                // 渲染网页
                _view.ExpandOneQuestioHtml(q, html);
                return true;
            }
            else
            {
                return false;
            }
        }
        void Judge(const std::string &number, const std::string &in_json, std::string &out_json)
        {
            // 1. 进行反序列化提取出code，获取题目id
            Json::Value in_value;
            Json::Reader reader;
            reader.parse(in_json, in_value);
            std::string code = in_value["code"].asString();
            // 2. 根据题目id获取测试代码，并拼接上用户提交的代码，填充code
            Question q;
            _model.GetOneQuestionByNumber(number, q);
            in_value["code"] = q.header + code + q.tail;
            in_value["cpu_limit"] = q.cpu_limit;
            in_value["mem_limit"] = q.mem_limit;

            Json::FastWriter writer;
            std::string compile_json = writer.write(in_value);
            // 3. 选择一台负载最小的主机
            while (1)
            {
                Machine *machine = nullptr;
                int id = _lb.AutoChoose(machine);
                if (id == -1)
                {
                    // 当前主机全部下线
                    break;
                }
                else
                {
                    // 4. 向该主机发起http请求
                    Client clt(machine->ip, machine->port);
                    // 负载加1
                    machine->IncLoad();
                    std::string info = "选择主机成功,id: " + std::to_string(id) + " ";
                    info += machine->ip + ":" + std::to_string(machine->port);
                    info += " 当前主机负载: " + std::to_string(machine->GetLoad());
                    // info += "当前主机负载情况：" + std::to_string(machine->_count)
                    LOG(INFO, info);
                    if (auto res = clt.Post("/compile_run", compile_json, "application/json;charset=utf-8"))
                    {
                        if (res->status == 200)
                        {
                            // 5. 构建json串并返回
                            out_json = res->body;
                            // 负载减1
                            machine->DecLoad();
                            break;
                        }
                        machine->DecLoad();
                    }
                    else
                    {
                        // 没有得到响应，当前主机可能下线了
                        LOG(ERROR, "主机" + std::to_string(id) + "可能已经下线 详情" + machine->ip + ":" + std::to_string(machine->port));
                        // 将主机离线
                        _lb.Offline(id);
                        _lb.ShowMachines();
                    }
                };
            }
        }
        int SignIn(const std::string &in_json, const std::string table, std::string &out_json)
        {
            return _model.SignIn(in_json, table, out_json);
        }
        bool Register(const std::string &in_json, std::string &out_json)
        {
            return _model.Register(in_json, out_json);
        }
        bool Forget(const std::string &in_json, std::string &out_json)
        {
            return _model.Forget(in_json, out_json);
        }
        std::string GetUserName(int id, const std::string &strId, const std::string &table)
        {
            return _model.GetUserName(id, strId, table);
        }
        void AddQuestion(const std::string &in_json, std::string &out_json)
        {
            _model.AddQuestion(in_json, out_json);
        }

    private:
        Model _model;
        View _view;

    public:
        LoadBlance _lb;
    };
}